using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

namespace WindowsApplication4
{
    public class Target
    {
        private Village targetVillage;


        private DataRow drTarget;
        private Compaigns compaigns;
        private TimeSpan timeBetweenAttackLandings;

        public class CONSTS
        {

            public class dtLaunchedAttacks
            {
                public class Columns
                {
                    public static int PriorityIndex = 1;
                    public static int LaunchIndex = 2;
                    public static int DurationIndex = 3;
                    public static int LandTimeIndex = 4;
                }
            }
        }

        public Target(DataRow target, Compaigns compaigns)
        {
            drTarget = target;
            this.compaigns = compaigns;
        }

        public Village TargetVillage
        {
            get
            {
                if (targetVillage == null)
                {
                    targetVillage = Worlds.currentWorld.Village(Convert.ToInt32(drTarget[Compaigns.CONSTS.dtTargets.Columns.VillageIDIndex]));

                    if (targetVillage == null)
                    {
                        throw new Exception("Unexpected. Cannot find village ID =" + drTarget[Compaigns.CONSTS.dtTargets.Columns.VillageIDIndex].ToString());
                    }
                }

                return targetVillage;
            }

            set
            {
                targetVillage = null;
                drTarget[Compaigns.CONSTS.dtTargets.Columns.VillageIDIndex] = value.ID;
            }
        }

        public string Description
        {
            get
            {
                return drTarget[Compaigns.CONSTS.dtTargets.Columns.DescriptionIndex].ToString();
            }
            set
            {
                drTarget[Compaigns.CONSTS.dtTargets.Columns.DescriptionIndex] = value;
            }
        }

        public int ID
        {
            get
            {
                return (int)drTarget[Compaigns.CONSTS.dtTargets.Columns.TargetIDIndex];
            }
        }

        public System.Collections.Generic.List<Attack> Attacks
        {
            get
            {
                DataRow[] rows = AttackRows;
                if (rows.Length == 0)
                {
                    return new List<Attack>(0);
                }
                else
                {
                    List<Attack> attacks = new List<Attack>(rows.Length);

                    foreach (DataRow dr in rows)
                    {
                        attacks.Add(new Attack(dr,this));
                    }

                    return attacks;
                }
            }
        }

        /// <summary>
        /// Only get a valid first attack when you first called ScheduleAttacks()
        /// </summary>
        public Attack FirstLandingAttack
        {
            get
            {                
                // get all attack for this target, sort by priority so that lowest priority is first
                DataRow[] drs = compaigns.dtAttacks.Select(Compaigns.CONSTS.dtAttacks.Columns.TargetIDName
                    + " = " + this.ID, Compaigns.CONSTS.dtAttacks.Columns.PriorityName+ " ASC"
                    , DataViewRowState.CurrentRows);

                if (drs.Length > 0)
                {
                    Attack firstAttack = new Attack(drs[0], this);
                    Attack currentAttack;

                    int firstPriority = firstAttack.Priority;
                    for (int i = 1; i < drs.Length; i++)
                    {
                        currentAttack = new Attack(drs[i], this);
                        if (currentAttack.Priority != firstPriority)
                        {
                            break;
                        }
                        if (currentAttack.LandTime < firstAttack.LandTime)
                        {
                            firstAttack = currentAttack;
                        }
                    }

                    return firstAttack;
                }
                else
                {
                    return null;
                }             
            }
        }

        /// <summary>
        /// Only get a valid first attack when you first called ScheduleAttacks()
        /// </summary>
        public Attack FirstAttackToBeLaunched
        {
            get
            {
                // get all attack for this target, sort by priority so that lowest priority is first
                DataRow[] drs = compaigns.dtAttacks.Select(Compaigns.CONSTS.dtAttacks.Columns.TargetIDName
                    + " = " + this.ID, Compaigns.CONSTS.dtAttacks.Columns.TimeLaunchedName + " ASC"
                    , DataViewRowState.CurrentRows);

                if (drs.Length > 0)
                {
                    return new Attack(drs[0], this);
                }
                else
                {
                    return null;
                }
            }
        }


        private DataRow[] AttackRows
        {
            get
            {
                return drTarget.GetChildRows(Compaigns.CONSTS.Relations.TargetToAttack);
            }
        }

        public bool AttacksLaunched_AllAttacksAway
        {
            get
            {
                bool allAttacksAway = true;
                foreach(Attack attack in Attacks) 
                {
                    if (!attack.Launched)
                    {
                        allAttacksAway = false;
                        break;
                    }
                }

                return allAttacksAway;
            }
        }

        /// <summary>
        /// specifies if the attacks at this target have been executed, ie, are being launched or have already been launched. 
        /// See AttacksLaunched_AllAttacksAway to tell you if ALL attacks have already been sent. 
        /// </summary>
        public bool AttacksLaunched
        {
            get
            {
                return (bool)drTarget[Compaigns.CONSTS.dtTargets.Columns.LaunchedIndex];
            }
            set 
            {
                drTarget[Compaigns.CONSTS.dtTargets.Columns.LaunchedIndex] = value;
            }
        }


        public void ScheduleAttacks(DateTime launchTime)
        {   
           this.timeBetweenAttackLandings = new TimeSpan(0,0,0,Convert.ToInt32( WindowsApplication4.Properties.Settings.Default.LandTimeDifference ));

            DataTable dt = LaunchAttacks(launchTime);

            DataRow[] rows = compaigns.dtAttacks.Select(Compaigns.CONSTS.dtAttacks.Columns.TargetIDName + "=" + this.ID);

            DataRow scheduleRow;
            foreach(DataRow dr in rows) {
                scheduleRow = dt.Rows.Find(dr[Compaigns.CONSTS.dtAttacks.Columns.AttackIDIndex]);
                dr[Compaigns.CONSTS.dtAttacks.Columns.TimeLaunchedIndex] = scheduleRow[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex];
            }

        }

        public void ScheduleAttacksByLandTime(DateTime LandTime)
        {
            this.timeBetweenAttackLandings = new TimeSpan(0, 0, 0, Convert.ToInt32(WindowsApplication4.Properties.Settings.Default.LandTimeDifference));

            DataTable dt = LaunchAttacks(DateTime.Now.AddMinutes(3));
            FixLaunchAttacksToLandTime(LandTime, dt);

            DataRow[] rows = compaigns.dtAttacks.Select(Compaigns.CONSTS.dtAttacks.Columns.TargetIDName + "=" + this.ID);

            DataRow scheduleRow;
            foreach (DataRow dr in rows)
            {
                scheduleRow = dt.Rows.Find(dr[Compaigns.CONSTS.dtAttacks.Columns.AttackIDIndex]);
                dr[Compaigns.CONSTS.dtAttacks.Columns.TimeLaunchedIndex] = scheduleRow[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex];
            }

        }

        //public void ScheduleAttacksByLandDate(DateTime landTime)
        //{
        //    this.timeBetweenAttackLandings = new TimeSpan(0, 0, 0, Convert.ToInt32(WindowsApplication4.Properties.Settings.Default.LandTimeDifference));

        //    DataTable dt = LaunchAttacks(DateTime.Now);

        //    DataRow[] rows = compaigns.dtAttacks.Select(Compaigns.CONSTS.dtAttacks.Columns.TargetIDName + "=" + this.ID);

        //    DataRow scheduleRow;
        //    foreach (DataRow dr in rows)
        //    {
        //        scheduleRow = dt.Rows.Find(dr[Compaigns.CONSTS.dtAttacks.Columns.AttackIDIndex]);
        //        dr[Compaigns.CONSTS.dtAttacks.Columns.TimeLaunchedIndex] = scheduleRow[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex];
        //    }

        //}


        private void FixLaunchAttacksToLandTime(DateTime landTime, DataTable dt)
        {
            if (dt.Rows.Count >= 1)
            {
                // get first landing attack
                DataRow[] drs = dt.Select("", "LandTime asc");

                DateTime firstLandTime = (DateTime)drs[0][CONSTS.dtLaunchedAttacks.Columns.LandTimeIndex];
//                dr[0][CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] =
  //                  ((DateTime)dr[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex]).Add(adjustement);

                TimeSpan adjustement = landTime - firstLandTime;

                foreach (DataRow dr in dt.Rows)
                {
                    dr[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] =
                        ((DateTime)dr[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex]).Add(adjustement);
                }                
            }
        }

        private DataTable LaunchAttacks(DateTime launchTime)
        {
            DataTable dtLaunchedAttacks;
            dtLaunchedAttacks = new DataTable("LaunchedAttacks");
            dtLaunchedAttacks.Columns.Add("AttackID", System.Type.GetType("System.Int32"));
            dtLaunchedAttacks.Columns.Add("Priority", System.Type.GetType("System.Int32"));
            dtLaunchedAttacks.Columns.Add("Launch", System.Type.GetType("System.DateTime"));
            dtLaunchedAttacks.Columns.Add("Duration", System.Type.GetType("System.TimeSpan"));
            dtLaunchedAttacks.Columns.Add("LandTime", System.Type.GetType("System.DateTime"));
            dtLaunchedAttacks.PrimaryKey = new DataColumn[] { dtLaunchedAttacks.Columns[0] };


            System.Collections.Generic.List<Attack> attacks = this.Attacks;

            if (attacks.Count != 0)
            {

                foreach (Attack attack in Attacks)
                {
                    dtLaunchedAttacks.Rows.Add(new object[] { attack.AttackID, attack.Priority, DateTime.MinValue, attack.TravelTime, DateTime.MinValue });
                }
                dtLaunchedAttacks.AcceptChanges();

                DataView view = new DataView(dtLaunchedAttacks, "", "Priority ASC, Duration DESC", DataViewRowState.CurrentRows);

                int firstPriority = Convert.ToInt32(view[0][CONSTS.dtLaunchedAttacks.Columns.PriorityIndex]);

                DateTime lastLandTime = ProcessFirstPriority(firstPriority, view, launchTime);
                ProcessNthPriority(firstPriority, view, lastLandTime);
                AndNowFixIt(dtLaunchedAttacks, launchTime);
            }

            return dtLaunchedAttacks;
        }

        private DateTime ProcessFirstPriority(int priority, DataView view, DateTime launchTime)
        {
            view.RowFilter = "Priority = " + priority.ToString();
            view.Sort = "Duration DESC";

            DateTime latestLandTimeInThisPriority;

            DataRowView row = view[0];
            DateTime lastLandTime = launchTime.Add((TimeSpan)row[CONSTS.dtLaunchedAttacks.Columns.DurationIndex]);
            row[CONSTS.dtLaunchedAttacks.Columns.LandTimeIndex] = lastLandTime;
            row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] = launchTime;
            latestLandTimeInThisPriority = lastLandTime;

            for (int i =1; i < view.Count; i++)
            {
                row = view[i];
                lastLandTime = lastLandTime.Subtract(this.timeBetweenAttackLandings);
                row = view[i];
                row[CONSTS.dtLaunchedAttacks.Columns.LandTimeIndex] = lastLandTime;
                row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] = lastLandTime.Subtract( 
                     (TimeSpan)row[CONSTS.dtLaunchedAttacks.Columns.DurationIndex]);
            }

            return latestLandTimeInThisPriority;
        }

        private void ProcessNthPriority(int lastPriority, DataView view, DateTime lastLandTime)
        {
            view.RowFilter = "Priority > " + lastPriority.ToString();
            view.Sort = "Priority ASC, Duration DESC";

           
            int currentPriority=0;
            DataRowView row;
            bool areThereMoreRows = false;
            for (int i = 0; i < view.Count; i++)
            {
                row = view[i];

                if (i == 0)
                {
                    currentPriority = (int)row[CONSTS.dtLaunchedAttacks.Columns.PriorityIndex];
                } else {
                    if ( currentPriority != (int)row[CONSTS.dtLaunchedAttacks.Columns.PriorityIndex] ) {
                        areThereMoreRows = true;
                        break;
                    }
                }

                lastLandTime = lastLandTime.Add(this.timeBetweenAttackLandings);
                row[CONSTS.dtLaunchedAttacks.Columns.LandTimeIndex] = lastLandTime;
                row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] = lastLandTime.Subtract(
                     (TimeSpan)row[CONSTS.dtLaunchedAttacks.Columns.DurationIndex]);
            }

            if (areThereMoreRows)
            {
                ProcessNthPriority(currentPriority, view, lastLandTime);
            }
        }

        private void AndNowFixIt(DataTable dt, DateTime launchTime)
        {
            //view.RowFilter = "";
            //view.Sort = "Launch ASC";


            DataRow[] drs = dt.Select("", "Launch ASC", DataViewRowState.CurrentRows);
            DataRow row;

            row = drs[0];
            DateTime lastAttackOldLaunchTime = (DateTime)row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex];
            DateTime lastAttackNewLaunchTime = launchTime;

            TimeSpan difference;
            for (int i = 0; i < drs.Length; i++)
            {
                row = drs[i];

                difference = (DateTime)row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] - lastAttackOldLaunchTime;
                lastAttackOldLaunchTime = (DateTime)row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex];
                lastAttackNewLaunchTime = lastAttackNewLaunchTime.Add(difference);
                row[CONSTS.dtLaunchedAttacks.Columns.LaunchIndex] = lastAttackNewLaunchTime;
                row[CONSTS.dtLaunchedAttacks.Columns.LandTimeIndex] = lastAttackNewLaunchTime.Add((TimeSpan)row[CONSTS.dtLaunchedAttacks.Columns.DurationIndex]);
            }


        }


        internal void Delete()
        {
            foreach (Attack attack in Attacks)
            {
                attack.Delete();
            }

            drTarget.Delete();
        }

        internal void AddAttack(Village village, int attackOrder, Unit unit, string desc)
        {
            compaigns.dtAttacks.Rows.Add(new object[] { null, this.ID, village.ID, unit.Name, desc, attackOrder });
        }

        internal Target Copy(Compaign compaign)
        {
            Target newTarget = compaign.AddTarget(this.TargetVillage, this.Description);

            foreach(Attack attack in this.Attacks) {
                newTarget.AddAttack(attack.AttackingVillage, attack.Priority, attack.AttackingUnit, attack.Description);
            }

            return newTarget;
            
        }

        internal void CancelAttack()
        {
            this.AttacksLaunched = false;
            foreach (Attack attack in Attacks)
            {
                attack.Launched = false;
            }

        }
    }
}
